package com.gitee.wsl.ext.string


/**
 * Splits the string, trims all and creates a set of the elements.
 */
fun String.splitAndTrimToSet(delimiter: String = ",") =
    split(delimiter)
        .filter { it.isNotBlank() }
        .map { it.trim() }
        .toSet()

/**
 * splits a [String] with the given [delimiter], but adds that delimiter to the resulting [List]
 *
 * eg.
 * ```kotlin
 * "foo,bar,baz".splitKeep(",") == listOf("foo", ",", "bar", ",", "baz")
 * ```
 */
fun String.splitKeep(delimiter: String): List<String> = split(Regex("(?<=[$delimiter])|(?=[$delimiter])"))

fun String.splitKeep(regex: Regex): List<String> {
    val str = this
    val out = arrayListOf<String>()
    var lastPos = 0
    for (part in regex.findAll(this)) {
        val prange = part.range
        if (lastPos != prange.first) {
            out += str.substring(lastPos, prange.first)
        }
        out += str.substring(prange)
        lastPos = prange.last + 1
    }
    if (lastPos != str.length) {
        out += str.substring(lastPos)
    }
    return out
}

/**
 * Splits a string given those delimiters.
 * [removedDelimiter] is removed after splitting,
 * whereas [keptDelimiter] is kept around.
 *
 * For example, if we split `this[0].thing`
 * we get back `listOf("this", "[0]", "thing")`.
 */
fun String.splitTwice(
    removedDelimiter: String = ".",
    keptDelimiter: String = "[",
): List<String> =
    split(removedDelimiter).flatMap {
        when {
            !it.contains('[') -> listOf(it)
            it.startsWith('[') ->
                // if it starts with '[', remove the first useless empty match
                it.split(keptDelimiter).drop(1).map { keptDelimiter + it }
            else -> {
                // we know there's a '[', and not at the beginning
                val (init, rest) = it.split(keptDelimiter, limit = 2)
                listOf(init) + rest.split(keptDelimiter).map { keptDelimiter + it }
            }
        }
    }

fun String.splitBySpace() = split("\\s+".toRegex())


// splitMap ------------------------------------------------------------------------------------------------------------

/**
 * Splits this strings using the specified [delimiter] applies [transform]
 * to each substrings and joins back the mapped substrings to a string using the same [delimiter].
 */
fun CharSequence.splitMap(
    delimiter: String,
    ignoreCase: Boolean = false,
    limit: Int = 0,
    transform: (String) -> String
): String = split(delimiter, ignoreCase = ignoreCase, limit = limit).joinToString(delimiter) { transform(it) }


// splitToSequence -----------------------------------------------------------------------------------------------------

/**
 * Splits this character sequence to a sequence of strings around occurrences of the specified [delimiters].
 *
 * @param delimiters One or more strings to be used as delimiters.
 * @param keepDelimiters `true` to have strings end with its corresponding delimiter.
 * @param ignoreCase `true` to ignore the character case when matching a delimiter. By default `false`.
 * @param limit The maximum number of substrings to return. Zero by default means no limit is set.
 *
 * To avoid ambiguous results when strings in [delimiters] have characters in common, this method proceeds from
 * the beginning to the end of this string, and finds at each position the first element in [delimiters]
 * that matches this string at that position.
 */
fun CharSequence.splitToSequence(
    vararg delimiters: String,
    keepDelimiters: Boolean = false,
    ignoreCase: Boolean = false,
    limit: Int = 0,
): Sequence<String> =
    rangesDelimitedBy(delimiters = delimiters, ignoreCase = ignoreCase, limit = limit)
        .run {
            if (!keepDelimiters) map { substring(it) }
            else windowed(size = 2, step = 1, partialWindows = true) { ranges ->
                substring(if (ranges.size == 2) ranges[0].first until ranges[1].first else ranges[0])
            }
        }


/**
 * Returns a sequence of index ranges of substrings in this character sequence around occurrences of the specified [delimiters].
 *
 * @param delimiters One or more strings to be used as delimiters.
 * @param startIndex The index to start searching delimiters from.
 *  No range having its start value less than [startIndex] is returned.
 *  [startIndex] is coerced to be non-negative and not greater than length of this string.
 * @param ignoreCase `true` to ignore the character case when matching a delimiter. By default `false`.
 * @param limit The maximum number of substrings to return. Zero by default means no limit is set.
 *
 * To avoid ambiguous results when strings in [delimiters] have characters in common, this method proceeds from
 * the beginning to the end of this string, and finds at each position the first element in [delimiters]
 * that matches this string at that position.
 */
internal fun CharSequence.rangesDelimitedBy(
    delimiters: Array<out String>,
    startIndex: Int = 0,
    ignoreCase: Boolean = false,
    limit: Int = 0,
): Sequence<IntRange> {
    require(limit >= 0) { "Limit must be non-negative, but was $limit." }
    val delimiterList = delimiters.asList()

    return DelimitedRangesSequence(this, startIndex, limit) { currentIndex ->
        findAnyOf(
            strings = delimiterList,
            startIndex = currentIndex,
            ignoreCase = ignoreCase,
            last = false
        )?.let { it.first to it.second.length }
    }
}


private fun CharSequence.findAnyOf(strings: Collection<String>, startIndex: Int, ignoreCase: Boolean, last: Boolean): Pair<Int, String>? {
    if (!ignoreCase && strings.size == 1) {
        val string = strings.single()
        val index = if (!last) indexOf(string, startIndex) else lastIndexOf(string, startIndex)
        return if (index < 0) null else index to string
    }

    val indices = if (!last) startIndex.coerceAtLeast(0)..length else startIndex.coerceAtMost(lastIndex) downTo 0

    if (this is String) {
        for (index in indices) {
            val matchingString = strings.firstOrNull { it.regionMatches(0, this, index, it.length, ignoreCase) }
            if (matchingString != null)
                return index to matchingString
        }
    } else {
        for (index in indices) {
            val matchingString = strings.firstOrNull { it.regionMatchesImpl(0, this, index, it.length, ignoreCase) }
            if (matchingString != null)
                return index to matchingString
        }
    }

    return null
}

/**
 * Implementation of [regionMatches] for CharSequences.
 * Invoked when it's already known that arguments aren't strings, so that no extra type checks are performed.
 */
//private fun CharSequence.regionMatchesImpl(thisOffset: Int, other: CharSequence, otherOffset: Int, length: Int, ignoreCase: Boolean): Boolean =
//    if ((otherOffset < 0) || (thisOffset < 0) || (thisOffset > this.length - length) || (otherOffset > other.length - length)) false
//    else (0 until length).all {
//        this[thisOffset + it].equals(other[otherOffset + it], ignoreCase)
//    }